
/*
 * sndpipe: A UNIX pipes-based audio stream processor toolkit.
 * Copyright (C) 2011  Bradley Worley
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to:
 *
 *   Free Software Foundation, Inc.
 *   51 Franklin Street, Fifth Floor
 *   Boston, MA  02110-1301, USA.
 */

/* include the sndpipe header. */
#include "sndpipe.h"

/* define the waveforms we are capable of generating. */
#define SP_WAVE_SIN 1
#define SP_WAVE_COS 2
#define SP_WAVE_TRI 3
#define SP_WAVE_SAW 4
#define SP_WAVE_SQR 5
#define SP_WAVE_PUL 6

/* define the waveform format for parsing waveform option strings. */
struct gen_waves {
  char *str;
  int wavid;
};

/* declare the array of accepted waveform formats. */
static struct gen_waves waves[] = {
  { "SIN", SP_WAVE_SIN },
  { "COS", SP_WAVE_COS },
  { "TRI", SP_WAVE_TRI },
  { "SAW", SP_WAVE_SAW },
  { "SQR", SP_WAVE_SQR },
  { "PUL", SP_WAVE_PUL },
  { SP_UNKNOWN, SP_UNKNOWN }
};

/* gen_get_wave: parses a string for waveform format information.
 *
 * this function pulls its information from the wave argument in the
 * options parsing array. values are compared to accepted values and,
 * if a match is found, the corresponding waveform integer is returned.
 * otherwise, a zero value is returned.
 */
int gen_get_wave (void) {
  /* declare required variables. */
  char *wave;
  int i;

  /* get the wave string from the parsed command-line arguments. */
  wave = opts_get_string (OPTS_S_WAVE);

  /* ensure the wave string is non-null. */
  if (!wave)
    return SP_UNKNOWN;

  /* loop through the array of known wave strings. */
  for (i = 0; waves[i].str; i++) {
    /* see if the currently indexed wave string is a match. */
    if (strcmp (wave, waves[i].str) == 0)
      return waves[i].wavid;
  }

  /* return zero. we failed to find a match. */
  return SP_UNKNOWN;
}

/* func_gen: initiates the GEN pipes functionality.
 *
 * this function opens up an input (if possible) and output pipe, and
 * adds synthesized waveform channels (the number of which is defined
 * by the --channels option) to the data stream.
 */
void func_gen (void) {
  /* declare required variables. */
  unsigned long c, i, channels, frames;
  struct pipes *pi, *po;
  double **buf;
  SF_INFO sfi;
  int wave;

  /* declare required waveform generation variables. */
  unsigned long decim, frame_id;
  double w_mag, w_freq, w_phase, w_offset, w_duty;

  /* read in the waveform options. */
  w_mag = opts_get_float (OPTS_S_AMPLITUDE);
  w_freq = opts_get_float (OPTS_S_FREQUENCY);
  w_phase = opts_get_float (OPTS_S_PHASE);
  w_offset = opts_get_float (OPTS_S_OFFSET);
  w_duty = opts_get_float (OPTS_S_DUTY);

  /* ensure the frequency option was supplied. */
  if (!w_freq) {
    /* print a warning message and set the default value. */
    warnf ("unknown waveform frequency. assuming 1.0 khz");
    w_freq = 1e3;
  }

  /* ensure the amplitude option was supplied. */
  if (!w_mag) {
    /* print a warning message and set the default value. */
    warnf ("unknown waveform amplitude. assuming full-scale");
    w_mag = 1.0;
  }

  /* read in the waveform type. */
  wave = gen_get_wave ();

  /* ensure the waveform type was supplied. */
  if (!wave) {
    /* print a warning message and use the default (SIN) value. */
    warnf ("unknown waveform type. assuming SIN");
    wave = SP_WAVE_SIN;
  }

  /* ensure the waveform duty cycle was supplied for PWM waveforms. */
  if (wave == SP_WAVE_PUL && !w_duty) {
    /* print a warning message and set the default value. */
    warnf ("unknown PUL waveform duty cycle. assuming 0.5");
    w_duty = 0.5;
  }

  /* zero out the sndfile information structure. */
  memset (&sfi, 0, sizeof (sfi));

  /* read in the channel count option. */
  channels = opts_get_int (OPTS_S_CHANNELS);

  /* ensure the channel count was supplied. */
  if (!channels) {
    /* just use a default value. this isn't worth a warning. */
    channels = 1;
  }

  /* attempt to open the input pipe. */
  pi = pipes_open_input ();

  /* determine whether the input pipe was opened successfully. */
  if (pi) {
    /* yes. use the existing format information and add to the channels. */
    sfi.channels = pi->sfi.channels + channels;
    sfi.samplerate = pi->sfi.samplerate;
    sfi.format = pi->sfi.format;
  }
  else {
    /* no. set up new format information. */
    sfi.channels = channels;
    sfi.samplerate = opts_get_int (OPTS_S_RATE);
    sfi.format = pipes_get_format ();

    /* make sure the sample rate was supplied. */
    if (!sfi.samplerate) {
      /* print a warning message and set the default value. */
      warnf ("unknown output sample rate. assuming 8.0 khz");
      sfi.samplerate = 8000;
    }
  }

  /* ensure we have a valid output pipes format. */
  if (!sfi.format) {
    /* print a failure message and exit the application. */
    failf ("unable to retrieve pipes output format");
  }

  /* open the output pipes structure and allocate the buffer memory. */
  frames = 0;
  po = pipes_open_output (sfi);
  buf = pipes_buf_alloc (po, &frames);

  /* initialize the frame identifier and calculate the decimation ratio. */
  frame_id = 0;
  decim = floor ((double) po->sfi.samplerate / w_freq);

  /* loop while we can write samples to the pipe. */
  do {
    /* if we are associated with an input pipe... */
    if (pi) {
      /* attempt to read from the input pipe. */
      if (pipes_read (pi, &buf, frames) != SP_OK) {
        /* print a failure message and break the loop. */
        warnf ("unable to read from input audio pipe");
        break;
      }
    }

    /* act based on the generated waveform. */
    switch (wave) {
      /* sine wave. */
      case SP_WAVE_SIN:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++)
            buf[c][i] = w_mag * sin (2.0 * M_PI * w_freq
              * (double) frame_id / (double) po->sfi.samplerate
              + (M_PI / 2.0) * w_phase) + w_offset;

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;

      /* cosine wave. */
      case SP_WAVE_COS:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++)
            buf[c][i] = w_mag * cos (2.0 * M_PI * w_freq
              * (double) frame_id / (double) po->sfi.samplerate
              + (M_PI / 2.0) * w_phase) + w_offset;

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;

      /* triangle wave. */
      case SP_WAVE_TRI:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++) {
            if (frame_id < decim / 2)
              buf[c][i] = 4.0 * w_mag * w_freq * (double) frame_id
                / po->sfi.samplerate - w_mag + w_offset;
            else
              buf[c][i] = -4.0 * w_mag * w_freq * (double) frame_id
                / po->sfi.samplerate + 3.0 * w_mag + w_offset;
          }

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;

      /* sawtooth wave. */
      case SP_WAVE_SAW:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++)
            buf[c][i] = 2.0 * w_mag * w_freq * (double) frame_id
              / po->sfi.samplerate - w_mag + w_offset;

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;

      /* square wave. */
      case SP_WAVE_SQR:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++) {
            /* divide the wave in half, first positive then negative. */
            if (frame_id < decim / 2)
              buf[c][i] = w_mag;
            else
              buf[c][i] = -w_mag;
          }

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;

      /* custom duty-cycle pulse wave. */
      case SP_WAVE_PUL:
        /* loop through the number of data frames. */
        for (i = 0; i < frames; i++) {
          /* and then through the number of output channels. */
          for (c = pi ? pi->sfi.channels : 0; c < po->sfi.channels; c++) {
            /* divice the wave by duty cycle, first positive then negative. */
            if (frame_id < decim * w_duty)
              buf[c][i] = w_mag;
            else
              buf[c][i] = -w_mag;
          }

          /* cycle the frame identifier through its period. */
          frame_id++; if (frame_id >= decim) frame_id = 0;
        }
        break;
    }
  } while (pipes_write (po, buf, frames) == SP_OK);

  /* free the output pipes buffer memory. */
  pipes_buf_free (po, frames, buf);

  /* attempt to close the input pipe. */
  if (pipes_close (pi) != SP_OK) {
    /* print a failure message and exit the application. */
    failf ("unable to close input pipe");
  }

  /* attempt to close the output pipe. */
  if (pipes_close (po) != SP_OK) {
    /* print a failure message and exit the application. */
    failf ("unable to close output pipe");
  }
}

